----------Talking Text Writer----------
A 4am crack                  2017-06-23
---------------------------------------

Name: Talking Text Writer
Version: 1987-06-01 (most recent file
  datestamp in ProDOS disk catalog)
Genre: educational
Year: 1986
Credits: Teresa J. Rosegrant, Russell
  A. Cooper
Publisher: Scholastic, Inc.
Platform: Apple //e or later (64K)
Media: 4 single-sided 5.25-inch disks
OS: ProDOS
Previous cracks: none
Similar cracks:
  #684 Math Shop v1986-10-27

I have 4 disks, labeled

  1. "reader"
  2. "writer"
  3. "file maker"
  4. "sample files"

I'll start with disk 1.

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but copy loads ProDOS then
  reboots

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Copy ][+ nibble editor
  nothing immediately suspicious

Disk Fixer
  T00 -> looks like ProDOS bootloader
         and ProDOS disk catalog

Why didn't any of my copies work?
  Probably a nibble check in the
  startup program

Next steps:

  1. Search for the error condition
     (maybe we'll get lucky!)
  2. Disable protection check that
     leads to the error condition
  3. Declare victory

(*) go to the gym

                   ~

               Chapter 1
        Jump, Jump For My Love


[S6,D1=non-working copy]

Turning to my trusty Disk Fixer sector
editor, I search my non-working copy
for "4C 00 C6" ("JMP $C600", to reboot
from slot 6).

                 --v--

------------- DISK SEARCH -------------

$04/$05-$37   $1C/$09-$E8

                 --^--

Two matches. T04,S05 seems like a dead
end. T1C,S09, on the other hand,
contains a veritable plethora of red
flags. A few of them that stand out
immediately:

- a JSR to a subroutine, then a
  conditional branch over a JMP which
  immediately reboots (so, this code is
  very intolerant of errors -- and very
  unfriendly!)

- another subroutine with a reference
  to "$C089,X" (a common way to turn on
  the drive motor manually)

- that subroutine also contains several
  instances of "$C08C,X" (to read the
  data latch of the drive)

It appears this code is loaded into
memory at $3000. Here it is through the
eyes of a sector editor:

                 --v--

T1C,S09
----------- DISASSEMBLY MODE ----------
; set up some parameters
0000:A9 40          LDA   #$40
0002:8D 88 30       STA   $3088
0005:A9 00          LDA   #$00
0007:8D 87 30       STA   $3087
000A:8D 89 30       STA   $3089
000D:8D 8A 30       STA   $308A

; get slot number x16 (standard ProDOS
; location)
0010:AD 30 BF       LDA   $BF30
0013:8D 86 30       STA   $3086
0016:20 69 30       JSR   $3069
.
.
.
; call ProDOS MLI
0069:20 00 BF       JSR   $BF00

; MLI command=#$80 (raw block read)
006C:80

; address of parameter table (set up
; earlier to point to block #$0000 into
; $4000)
006D:85 30

; immediately return regardless of
; success or failure
006F:60             RTS

Continuing from $3019...

; Z flag set -> branch over next line

0019:F0 03          BEQ   $001E

; if Z flag is clear, there was some
; error with the ProDOS MLI call, so we
; end up at $30E8, which reboots slot 6
001B:4C E8 30       JMP   $30E8
.
.
.
00E8:4C 00 C6       JMP   $C600

That's the behavior I saw in my non-
working copy.

Continuing from $301E...

; get slot number x16 again
001E:AE 30 BF       LDX   $BF30

; call a subroutine (will investigate)
0021:20 9B 30       JSR   $309B

; and store result (!)
0024:85 1A          STA   $1A
.
.
.
; turn on drive motor manually
009B:BD 89 C0       LDA   $C089,X

; some sort of Death Counter
009E:A9 56          LDA   #$56
00A0:85 1C          STA   $1C
00A2:A9 08          LDA   #$08
00A4:C6 1B          DEC   $1B
00A6:D0 04          BNE   $00AC
00A8:C6 1C          DEC   $1C

; If the Death Counter hits zero, that
; would be bad. Which part of "Death
; Counter" sounded good to you, anyway?
; (Specifically, this jumps to the
; immediate reboot we saw earlier.)
00AA:F0 3C          BEQ   $00E8

; look for an #$FB nibble
00AC:BC 8C C0       LDY   $C08C,X
00AF:10 FB          BPL   $00AC
00B1:C0 FB          CPY   #$FB
00B3:D0 ED          BNE   $00A2
00B5:F0 00          BEQ   $00B7

; kill a few cycles (not pointless,
; because the disk spins independently
; of the CPU, so all of these low-level
; disk reads are highly time-sensitive)
00B7:EA             NOP
00B8:EA             NOP

; read data latch (note: no BPL loop
; here, we're just reading it once)
00B9:BC 8C C0       LDY   $C08C,X

; do a compare to set or clear the
; carry bit (among other things, but
; it's the carry bit we care about)
00BC:C0 08          CPY   #$08

; rotate the carry into the low bit of
; the accumulator
00BE:2A             ROL

; if we just rolled a "1" bit out of
; the high bit of the accumulator, take
; this branch
00BF:B0 0B          BCS   $00CC

; next nibble needs to be #$FF
00C1:BC 8C C0       LDY   $C08C,X
00C4:10 FB          BPL   $00C1
00C6:C0 FF          CPY   #$FF

; ...otherwise we start over
00C8:D0 D8          BNE   $00A2

; loop back to get next nibble
00CA:F0 EB          BEQ   $00B7

; execution continues here (from $30BF)
; and we get another (full) nibble
00CC:BC 8C C0       LDY   $C08C,X
00CF:10 FB          BPL   $00CC

; stash it in zero page
00D1:84 1B          STY   $1B

; if the accumulator is anything but
; %00001010, start over
00D3:C9 0A          CMP   #$0A
00D5:D0 CB          BNE   $00A2

; get one more nibble
00D7:BD 8C C0       LDA   $C08C,X
00DA:10 FB          BPL   $00D7

; AND it with the previously stashed
; nibble, to get a single 4-and-4
; encoded byte value
00DC:38             SEC
00DD:2A             ROL
00DE:25 1B          AND   $1B
00E0:49 FF          EOR   #$FF

; branch on failure
00E2:F0 07          BEQ   $00EB

; turn off drive motor manually
00E4:DD 88 C0       CMP   $C088,X

; and exit gracefully to the caller
00E7:60             RTS

I got lost several times trying to
follow this routine. I think the
easiest way to explain it is to show
the difference between the original
disk and my non-working copy.

                   ~

               Chapter 2
        It's Time To Get Visual


Here is the original disk, as seen by
the Copy II+ nibble editor. Nibbles
with extra "0" bits (timing bits) after
them are displayed in inverse on an
original machine, marked here with a
"+" after the nibble.

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 00  START: 1B1E  LENGTH: 17C1

1C70: 9F EB E5 FC D7 D7 D7 EE   VIEW
1C78: FA E6 E6 FF FE F2 ED FD
1C80: FF EF ED BA BB DD AF E6
1C88: B7 A7 CB B7 DE AA EB FF
1C90: FF FF FF FB+FF FF+FF FF+
1C98: FD FF+FF+FF+FF+FF+FF+FF+
1CA0: FF+FF+D5 AA 96 AA AB AA
1CA8: AA AA AB AA AA DE AA EB+
1CB0: FF+FF+FF+FF+FF+FF D5 AA
---------------------------------------

  A  TO ANALYZE DATA  ESC TO QUIT

  ?  FOR HELP SCREEN  /  CHANGE PARMS

  Q  FOR NEXT TRACK   SPACE TO RE-READ

                 --^--

It's easy to understand why a simple
sector copy failed. The sequence that
this code is looking for starts at
offset $1C93, which is between the end
of one sector and the beginning of the
next. (The data epilogue is at $1C8C;
the next address prologue is at $1CA2.)
Sector copiers discard everything
between those delimiters and rebuild
the track with a default pattern of
sync bytes. That pattern doesn't
include an $FB nibble, so the nibble
check fails.

But the EDD bit copy also failed. Here
is the original disk's pattern at
offset $1C93:

  - #$FB + timing bit
  - #$FF
  - #$FF + timing bit
  - #$FF
  - #$FF + timing bit

And here is what the same part of the
track looks like on my failed EDD copy:

                 --v--

1C70: 9F EB E5 FC D7 D7 D7 EE
1C78: FA E6 E6 FF FE F2 ED FD
1C80: FF EF ED BA BB DD AF E6
1C88: B7 A7 CB B7 DE AA EB FF
1C90: FF FF FF FB+FF FF FF+FF+
1C98: FD FF+FF+FF+FF+FF+FF+FF+
1CA0: FF+FF+D5 AA 96 AA AB AA
1CA8: AA AA AB AA AA DE AA EB+
1CB0: FF+FF+FF+FF+FF+FF D5 AA

                 --^--

A subtle difference! The sequence at
offset $1C93 now looks like this:

  - #$FB + timing bit
  - #$FF
  - #$FF
  - #$FF + timing bit
  - #$FF + timing bit

This code is looking for #$FF nibbles
with an alternating pattern of timing
bit, no timing bit, timing bit, no
timing bit. It doesn't find that on the
bit copy, so it knows it's not running
on an original disk.

                   ~

               Chapter 3
      That's What Friends Are For


This protection check has side effects;
it sets the accumulator to the value of
some bit math performed on two nibbles
read from disk. I don't know what those
values are, so I don't know what the
accumulator is when the protection
routine returns to the caller, so I
don't know what value ends up in zero
page $1A.

I also don't know if it matters or not,
but I'm going to err on the side of
caution and assume that it does. I have
seen this protection check before, but
not this variation that stores the
final value of the accumulator.

So... how to capture it? I could boot
trace the entire disk, but that's...
boring. I could sector-edit the code on
the original disk, but NEVER DO THIS
DON'T EVEN ASK WHY JUST NO. Instead,
I'm going to take advantage of the fact
that I have two disk drives, and that
this protection check takes a slot (and
drive) on entry, and I'm going to hack
a copy.

[S6,D1=original disk]
[S5,D1=non-working copy]

Editing the NON-WORKING COPY with the
Disk Fixer sector editor, I can make
two small changes to this sector:

; Instead of getting the current slot+
; drive from ProDOS, hard-code it to
; slot 6, drive 1. Do this before each
; JSR (the raw block read and the
; protection check itself)
T1C,S09,$10: AD30BF -> EAA960
T1C,S09,$1E: AE30BF -> EAA260

]PR#5
...reboots slot 5...
...briefly accesses slot 6...
...continues booting from slot 5...

Hooray! We have a bootable copy... as
long as the original disk is in slot 6.

One more edit (TO THE COPY OF COURSE),
to print out the accumulator after the
protection check passes:

T1C,S09,$24: 851AA00BB95C ->
             20DAFD4C69FF

]PR#5
...reboots slot 5...
...briefly accesses slot 6...
03
<beep>

Excellent. The value of the accumulator
on exit is #$03.

Starting over with a fresh (but non-
working) copy of the original disk, I
can make a small edit to the protection
routine so it sets the accumulator to
#$03 and exits unconditionally.

[S6,D1=fresh copy of disk 1]

Disk 1:
T1C,S09,$9A: BD89C0 -> A90360

]PR#6
...works...

Disk 2 ("writer") has an identical
protection routine on track $1D.

Disk 2:
T1D,S03,$9A: BD89C0 -> A90360

Disks 3 and 4 are unprotected.

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 1270
------------------EOF------------------
